import driver from '../driver.js'
import log from '../../dxmodules/dxLogger.js'
import common from '../../dxmodules/dxCommon.js'
import CryptoES from '../../dxmodules/crypto-es/index.js';
import utils from '../common/utils/utils.js';
import config from '../../dxmodules/dxConfig.js'
const safeService = {}

/**
 * Security module door opening command
   Authentication Process:
   The host computer sends command 0x07 to obtain a random number (32 bytes).
   The host computer encrypts this random number to obtain V.
   The host computer sends this command to perform authentication.

   Note: 
   P1=0x01 uses AES key area 1; 
   P1=0x11 uses AES key area 2; 
   P1=0x02 uses SM2 key area 1; 
   P1=0x22 ​​uses SM2 key area 2;
   P1=0x04 uses SM4 key area 1; 
   P1=0x44 uses SM4 key area 2.
 */
safeService.open = function () {
    let random = this.getRandomNumber();
    if (utils.isEmpty(random)) {
        return;
    }
    let type = config.get('sysInfo.safe_keytype');
    let secretType;
    let head = "55AA21";
    //encryption
    switch (type) {
        case 1:
            log.info("Security module AES encryption")
            secretType = "01";
            let key = CryptoES.enc.Hex.parse(config.get('sysInfo.safe_key_pub'));
            let iv = CryptoES.enc.Hex.parse(config.get('sysInfo.safe_iv_pri'));
            let plaintext = CryptoES.enc.Hex.parse(random);
            let encrypted = CryptoES.AES.encrypt(plaintext, key, { iv: iv, mode: CryptoES.mode.CBC, padding: CryptoES.pad.Pkcs7 });
            let hexDate = "01" + encrypted.ciphertext.toString(CryptoES.enc.Hex);
            log.info("-------Random number encryption result---------" + hexDate)
            let hexLen = (hexDate.length / 2).toString(16).padStart(2, '0') + "00";
            let date = head + hexLen + hexDate;
            let bcc = common.calculateBcc(common.hexToArr(date)).toString(16)
            date = date + bcc;
            //Send open command
            log.info("--------date--------" + date)
            driver.uart485.sendVg(date)
            break;
        case 2:
            log.info("Security Module SM2 Encryption")
            break;
        case 3:
            log.info("Security Module SM4 Encryption")
            break;
        default:
            log.error("No such cmdType ")
            break;
    }


}

/**
 * Modify security module switch
 * @param {*} data 0 Off 1 On 2 Reset Key
 */
safeService.updateSafeOpen = function (data) {
    if (data === 2) {
        log.info("Key Reset")
        config.setAndSave("sysInfo.safe_key_pub", "5de3dd7f5b725c7d27f36019a2ddd94f3866e0d383c93bd51780e938a2ddda4f");
        config.setAndSave("sysInfo.safe_iv_pri", "39fe007b83c92bd527f36019a2ddd94f5da3dd7f1780e93828e3601a1d8cb9f8");
        //Enable security module at the same time
        config.setAndSave("sysInfo.safe_open", 1);
    } else {
        config.setAndSave("sysInfo.safe_open", data);
    }


}
safeService.updateConfig = function (key, value) {
    switch (key) {
        case "safe_key_pub":
            log.info("Modify security module key: " + value);
            this.updateSecretKey({ "aes_key": value, "iv": config.get("sysInfo.safe_iv_pri") });
            break;
        case "safe_iv_pri":
            log.info("Modify security module iv: " + value);
            this.updateSecretKey({ "aes_key": config.get("sysInfo.safe_key_pub"), "iv": value });
            break;
        case "safe_open":
            log.info("Modify safety switch: " + value);
            this.updateSafeOpen(value);
            break;
        default:
            log.error("Error security module configuration items");
            break;
    }
}


/**
 * Modify the relay action time of the safety module
 */
safeService.updateRelayd = function (time) {
    if (config.get('sysInfo.safe_open') === 1) {
        log.info("Modify the relay action time of the safety module: " + time)
        time = decimalToHexLE(time / 50);
        if (utils.isEmpty(time)) {
            return;
        }
        let date = "55aa450100" + time
        let bcc = common.calculateBcc(common.hexToArr(date)).toString(16)
        date = date + bcc
        driver.uart485.sendVg(date)
        let result = driver.sync.request("updateRelayd", 2000)
        if (result === "00") {
            return true;
        }
        return false;
    }

}
/**
 * Relay open for a specified duration
 * @param {*} time 
 */
safeService.openDoorByTime = function (time) {
    //1.Modify relay time
    let result = this.updateRelayd(time);
    if (result) {
        log.info("Successfully modified the specified door opening time")
        //2.Open the door
        this.open()
    } else {
        log.error("Failed to modify specified door opening time");
    }
    let openDoorResult = driver.sync.request("openDoor", 2000)
    log.info("Door opening completed, result:" + openDoorResult)
    //3.Reset relay action time after door opening
    this.updateRelayd(config.get("sysInfo.relayd"))

}


/**
 * Modify key
 * Explanation:
 * Plaintext format of V: ​​Key Parameter (TLV) + Random Number (TLV format) + Padding Data (TLV) + Signature (SHA256)
 * Key Parameter TLV: T=0x01 L (1 byte): Number of bytes in V
 * Random Number TLV: T=0x03 L= X (1 byte) V: Random number obtained from the device
 * Padding Data (TLV): T=0x04 L= X (1 byte) The length of the entire plaintext V must be a multiple of the old key length. This item may not exist; the padding data is arbitrary.
 * Signature (SHA256): P1 + V plaintext (excluding the SHA256 field)
 * 
 * Encryption method of V:
 * 1. Encrypt using the old key of the modified key.
 * Key modification process:
 * 1. Obtain a random number from the device.
 * 2. Calculate the ciphertext according to this instruction format and then send it to the device.
 * @param {} date 
 */
safeService.updateSecretKey = function (date) {
    log.info("Modify key" + JSON.stringify(date))
    let random = this.getRandomNumber();
    if (utils.isEmpty(random)) {
        return;
    }
    let type = config.get('sysInfo.safe_keytype');
    let secretType;
    let head = "55AA0b";
    //encryption
    switch (type) {
        case 1:
            log.info("Security module AES encryption")
            secretType = "01";
            //Key parameters: New key aes_key + iv (the length of aes_key and the length of iv are equal)
            let aesKeyType = "01";
            let aesKeyValue = date.aes_key + date.iv;
            log.info("------------aesKeyValue: " + aesKeyValue)
            let aesKeyLen = (aesKeyValue.length / 2).toString(16).padStart(2, '0');
            let aesKeyDate = aesKeyType + aesKeyLen + aesKeyValue
            log.info("New key: " + aesKeyDate)
            //random numbers
            //  random="e497f897e85d7053c871bdb633b5674dfa56f477edc23d42352b9300c0e6b898"
            let randomType = "03";
            let randomValue = random;
            let randomLen = (randomValue.length / 2).toString(16).padStart(2, '0');
            let randomDate = randomType + randomLen + randomValue
            log.info("random numbers: " + randomDate)
            //Complete the data: Ensure that the key parameter plus the random number is a multiple of the new key.
            let completionValue = checkDivisibility(aesKeyDate + randomDate, config.get("sysInfo.safe_key_pub"));
            log.info("completionValue: " + completionValue)
            // let completionDate = "041a0000000000000000000000000000000000000000000000000000";
            log.info("Supplementary data: " + completionValue)
            let value = aesKeyDate + randomDate + completionValue
            log.info("Combined data: " + value)
            //Sign the data (SHA256)
            const sha256Digest = CryptoES.SHA256(CryptoES.enc.Hex.parse(secretType + value)).toString(CryptoES.enc.Hex);
            log.info("sign: " + sha256Digest)
            value = value + sha256Digest
            log.info("Complete data: " + value)
            // Encrypt the entire system with AES encryption using the old key.
            let oldKey = CryptoES.enc.Hex.parse(config.get('sysInfo.safe_key_pub'));
            let oldIv = CryptoES.enc.Hex.parse(config.get('sysInfo.safe_iv_pri'));
            log.info("--------oldKey--------" + oldKey)
            log.info("--------oldIv--------" + oldIv)
            let plaintext = CryptoES.enc.Hex.parse(value);
            let encrypted = CryptoES.AES.encrypt(plaintext, oldKey, { iv: oldIv, mode: CryptoES.mode.CBC, padding: CryptoES.pad.NoPadding });
            let hexDate = encrypted.ciphertext.toString(CryptoES.enc.Hex);

            hexDate = "01" + hexDate;
            log.info("01+AES encryption: " + hexDate)
            let len = (hexDate.length / 2).toString(16).padStart(2, '0') + "00"
            //Assemble and send instructions
            let keyDate = head + len + hexDate
            let bcc = common.calculateBcc(common.hexToArr(keyDate)).toString(16)
            keyDate = keyDate + bcc;
            //Send open command
            log.info("Send data" + keyDate)
            driver.uart485.sendVg(keyDate)
            //Waiting for the result
            let updateKeyResute = driver.sync.request("updateKey", 2000)
            //Timeout detection
            if (utils.isEmpty(updateKeyResute) || random == "") {
                log.error("Set key timeout")
                return;
            }
            log.info("updateKeyResute-----------------"+updateKeyResute)
            if (updateKeyResute == "00") {
                log.info("Key successfully set.")
                // Update with new key
                config.setAndSave('sysInfo.safe_key_pub', date.aes_key);
                config.setAndSave('sysInfo.safe_iv_pri', date.iv);
            } else {
                log.error("Key setup failed.")
                return;
            }

            break;
        case 2:
            log.info("Security Module SM2 Encryption")
            break;
        case 3:
            log.info("Security Module SM4 Encryption")
            break;
        default:
            log.error("No such cmdType ")
            break;
    }
}

safeService.getRandomNumber = function () {
    // Get random numbers
    driver.uart485.sendVg("55aa07010020d9")
    let random = driver.sync.request("getRandom", 2000)
    // Timeout detection
    if (utils.isEmpty(random) || random == "") {
        log.error("Random number generation timed out")
        return null;
    }
    log.info("--------random--------" + random)
    return random;
}


function decimalToHexLE(decimal) {
    // Limited to 2 bytes
    if (decimal < 0 || decimal > 255) {
        log.error("The input must be an integer between 0 and 255.");
        return null;
    }
    // Convert decimal to 2-byte hexadecimal.
    let hexString = decimal.toString(16).padStart(2, '0');
    return hexString;
}

function checkDivisibility(date, newSafeKey) {
    // Get half the length of A and B
    let dateLen = Math.floor(date.toString().length / 2);
    let newSafeKeyLen = Math.floor(newSafeKey.toString().length / 2);
    log.info("dateLen----" + dateLen);
    log.info("newSafeKeyLen----" + newSafeKeyLen);
    // Calculate the remainder
    const remainder = dateLen % newSafeKeyLen;
    log.info("Remainder----" + newSafeKeyLen);
    let patch;

    if (dateLen === 0) {
        return ""; // If the original data is 0, return an empty string directly.
    }

    // Calculate the value of the completed data based on the remainder.
    if (remainder === 1) {
        patch = "04";
    } else if (remainder === 2) {
        patch = "0400";
    } else {
        let patchLen = newSafeKeyLen - remainder; // Calculate len using lenB and remainder.
        log.info("Complete the number----" + patchLen);
        patch = "04" + (patchLen - 2).toString(16).padStart(2, '0') + "00".repeat(patchLen - 2); // padStart ensures a length of multiples of 2
    }

    return patch;
}
export default safeService
